`timescale 1ns/1ps
`default_nettype none

module cxy_fps_sync #(
    parameter   MAX_PHASE_ERROR = 20000
    ) 
    (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_rst_n,
    // config
    input  wire         I_cfg_output_enable,  // 输出使能
    input  wire         I_cfg_fps_sync_en,    // 帧率同步使能
    input  wire [7:0]   I_cfg_gclk_cycle,     // 默认gclk时钟分频数
    input  wire [7:0]   I_cfg_min_gclk_cycle, // 最小gclk时钟分频数
    input  wire [19:0]  I_cfg_gclk_total,     // 一帧时间内的gclk数-1
    input  wire [20:0]  I_cfg_frame_blank,    // 空白周期数
    // frame
    input  wire         I_frame_sync,
    // display control
    output wire         O_display_reset,       // 强制重新开始串移
    input  wire         I_display_ready,       // 输出模块ready
    input  wire         I_display_end,         // 输出模块显示完一帧
    output wire [7:0]   O_display_gclk_low,    // gclk低电平时钟数
    output wire [7:0]   O_display_gclk_cycle,  // gclk整周期时钟数
    output wire [19:0]  O_display_gclk_extra   // gclk额外周期数
    );
//------------------------Parameter----------------------
// fsm
localparam [2:0]
    IDLE  = 0,
    FREE  = 1,
    PREP  = 2,
    RESET = 3,
    SYNC  = 4;

localparam
    RESET_DELAY     = 1, // 复位输出模块需要的时间
    INIT_PHASE_DIFF = MAX_PHASE_ERROR - RESET_DELAY,
    MAX_PHASE_DIFF  = MAX_PHASE_ERROR * 2;

//------------------------Local signal-------------------
// fsm
reg  [2:0]  state;

// calc
reg  [20:0] blank_cnt;
reg         blank_over;
reg  [7:0]  tmp_gclk_cycle;
reg  [19:0] tmp_extra_cycle;

// phase
reg  [15:0] phase_cnt;
reg         phase_error;

// result
reg  [7:0]  gclk_cycle;
reg  [19:0] extra_cycle;

//------------------------Instantiation------------------

//------------------------Body---------------------------
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
//state[2:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        state <= IDLE;
    else if(!I_cfg_output_enable || !I_cfg_fps_sync_en || !I_display_ready)
        state <= IDLE;
    else
    case(state)
        IDLE:   state <= FREE;

        FREE:
            if(I_frame_sync)
                state <= PREP;

        PREP:
            if(I_frame_sync)
            begin
                if(tmp_gclk_cycle<I_cfg_min_gclk_cycle)
                    state <= FREE;
                else
                    state <= RESET;
            end

        RESET:
            if(phase_cnt==INIT_PHASE_DIFF)
                state <= SYNC;

        SYNC:
            if((I_frame_sync && tmp_gclk_cycle<I_cfg_min_gclk_cycle) || phase_error)
                state <= FREE;

        default:    state <= IDLE;
    endcase

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++calc+++++++++++++++++++++++++++
//blank_cnt[20:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        blank_cnt <= 'b0;
    else if(I_frame_sync)
        blank_cnt <= I_cfg_frame_blank;
    else if(blank_cnt>0)
        blank_cnt <= blank_cnt - 1'b1;

//blank_over
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        blank_over <= 'b0;
    else if(I_frame_sync)
        blank_over <= 'b0;
    else if(blank_cnt==1)
        blank_over <= 1'b1;

//tmp_extra_cycle[19:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tmp_extra_cycle <= 'b0;
    else if(I_frame_sync)
        tmp_extra_cycle <= 1'b1;
    else if(blank_over)
    begin
        if(tmp_extra_cycle==I_cfg_gclk_total)
            tmp_extra_cycle <= 'b0;
        else
            tmp_extra_cycle <= tmp_extra_cycle + 1'b1;
    end

//tmp_gclk_cycle[7:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        tmp_gclk_cycle <= 'b0;
    else if(I_frame_sync)
        tmp_gclk_cycle <= 'b0;
    else if(tmp_extra_cycle==I_cfg_gclk_total && tmp_gclk_cycle != 8'hff)
        tmp_gclk_cycle <= tmp_gclk_cycle + 1'b1;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++phase++++++++++++++++++++++++++
//phase_cnt[15:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        phase_cnt <= 1'b1;
    else if(I_frame_sync)
        phase_cnt <= 1'b1;
    else if(phase_cnt != 16'hffff)
        phase_cnt <= phase_cnt + 1'b1;

//phase_error
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        phase_error <= 'b0;
    else if(I_display_end && phase_cnt>MAX_PHASE_DIFF)
        phase_error <= 1'b1;
    else
        phase_error <= 'b0;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++result+++++++++++++++++++++++++
assign O_display_reset      = (state==RESET);
assign O_display_gclk_low   = gclk_cycle >> 1;
assign O_display_gclk_cycle = gclk_cycle;
assign O_display_gclk_extra = extra_cycle;

//gclk_cycle[7:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        gclk_cycle <= 'b0;
    else if(state==IDLE || state==FREE)
        gclk_cycle <= I_cfg_gclk_cycle;
    else if((state==RESET || state==SYNC) && I_frame_sync)
        gclk_cycle <= tmp_gclk_cycle;

//extra_cycle[19:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        extra_cycle <= 'b0;
    else if(state==IDLE || state==FREE)
        extra_cycle <= 'b0;
    else if((state==RESET || state==SYNC) && I_frame_sync)
        extra_cycle <= tmp_extra_cycle;
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
